Jx.declare('Jx.event.Dispatcher', {

	$extend: 'Jx.lib.Hash',

	$actions: [

		'listen',

		'unlisten',

		'clear',

		'clearListeners',

		'dispatch',

		'createEvent',

		'createListener',
		
		'setBubbleTarget',
		
		'unsetBubbleTarget'

	],

	inValidListeners: [

		'scope',

		'delay',

		'buffered',

		'interval',

		'once'

	],

	observableClass: 'Jx.Base',

	listenerClass: 'Jx.event.Listener',

	objectClass: 'Jx.event.Object',

	listenerClassIndex: {},

	objectClassIndex: {},

	observable: null,

	strictAccess: false,

	suspended: false,
	
	bubblesTo: null,

	constructor: function(observable){

		if (Jx.instanceOf(observable, this.observableClass)){

			this.$super();

			this.observable = observable;

			this.initializeEvents(observable);

		} else {

			throw new Error('invalid observable');

		}

	},

	initializeEvents: function(){

	},

	onDestroy: function(){
		
		this.unsetBubbleTarget();

		this.clear();

		this.$super();

	},


	onCallDispatch: function( listener, eventObject ){

		listener.handler()( eventObject );

	},

	onCallStart: function(listener, eventObject){

	},

	onCallEnd: function(listener, eventObject){

		var scope = listener.scope,

			args = eventObject.arguments;

		if (!this.suspended){

			this.onCall(

				listener,

				eventObject,

				listener.callback(),

				listener.scope(),

				Jx.isArray(args) ?

					[ eventObject ].concat(args)

					:

					[ eventObject ]

			);

		}

	},

	onCall: function(listener, eventObject, callback, scope, args){

		callback.apply(

			typeof scope == 'undefined' ?

				null

				:

				scope,

			args

		);

	},

	onListen: function(operation){

		var listeners = operation.listeners;

		for(var o, listener, collection, l = listeners.length; l--;){

			listener = listeners[l];

			this.createListener(listener.name, listener);

		}

		return this;

	},

	onUnlisten: function(operation){

		var listeners = operation.listeners,

			cache = {}, listenerObjects;

		for(var o, listener, name, fn, scope, l = listeners.length; l--;){

			listener = listeners[l];

			name = listener.name();

			fn = listener.callback();

			scope = listener.scope();

			//-- get listener
			if (!cache.hasOwnProperty(name)){

				cache[name] = this.get(name);

			}

			listenerObjects = cache[name];

			//-- found
			if (listenerObjects){

				for(var listenerObject, ll = listenerObjects.length; ll--;){

					listenerObject = listenerObjects[ll];

					if (listenerObject.callback() === fn && listenerObject.scope() === scope){

						listenerObject.destroy();

					}

				}

				//-- remove from listeners
				if (!listenerObjects.length){

					this.unset(name);

				}

			}

		}

	},

	onClearListeners: function(operation, name){

		var listeners = operation.listeners;

		for(var l = listeners.length; l--;){

			this.onDestroyListener( listeners[l], l);

			listeners.splice(l, 1);

		}
		
		this.unset(name);

	},

	onClear: function(){

		Jx.each(this.$cache, function(n, v, o){

			this.clearListeners(n);

		}, this);

	},

	onDispatch: function(operation, eventObject){

		var listeners = operation.listeners,
		
			target = eventObject.target();
			
		if (target && !target.isDestroyed()){

			//-- create dispatch
			for(var listener, l = listeners.length; l--; ){
	
				if ((listener = listeners[l]).isDestroyed()){
	
					listeners.splice(l, 1);
	
				} else {
	
					this.onCallDispatch( listener, eventObject );
	
				}
				
				if ( target.isDestroyed() ){
					
					break;
					
				}
	
			}
		
		}

		operation.returnValue = eventObject.value();

	},
	
	onAfterDispatch: function(operation, eventObject){
		
		var bubbleTarget = this.bubblesTo;
		
		if (bubbleTarget){
			
			if (bubbleTarget.isDestroyed()){
				
				this.unsetBubbleTarget();
				
			} else {
			
				bubbleTarget.event.dispatch(eventObject);
			
			}
			
		}
		
	},

	onCreateEvent: function(operation){

		operation.returnValue = Jx.instantiate(operation.className, [ operation.config ]);

	},

	onCreateListener: function(operation){

		operation.returnValue = Jx.instantiate(

			operation.className,

			[ this, operation.config ]

		);

	},

	onAfterCreateListener: function( operation ){

		var name = operation.name,

			collection = this.get(name);

		if (!collection){

			this.set(name, collection = []);
			
			this.onCreateListenerGroup( name, collection );

		}

		collection.push( operation.returnValue );

	},
	
	onCreateListenerGroup: function(name, collection){
		
	},
	
	onDestroyListenerGroup: function(name){
		
	},

	onDestroyListener: function( listener ){

		//-- destroy
		var collection = this.get(listener.name()),

			index;

		if (Jx.isArray(collection) &&

			(index = collection.lastIndexOf(listener)) != -1

		){

			collection.splice(index, 1);
			
			if (!collection.length){
				
				this.onDestroyListenerGroup(listener.name());
				
			}

		}
		
		

	},
	
	onSetBubbleTarget: function(operation, target){
		
		this.bubblesTo = target;
		
	},
	
	onUnsetBubbleTarget: function(operation, target){
		
		delete this.bubblesTo;
		
	},

	allowListen: function(operation, name, fn, scope, options){

		return !!(operation.listeners = this.formatListeners(name, fn, scope, options));

	},

	allowUnlisten: function(operation, name, fn, scope, options){

		return !!(operation.listeners = this.formatListeners(name, fn, scope, options));

	},

	allowClearListeners: function(operation, name){

		return Jx.isArray(operation.listeners = this.isSet(name) && this.get(name));

	},

	allowDispatch: function(operation, eventObject){

		operation.returnValue = undefined;

		return Jx.instanceOf(eventObject, this.objectClass) &&

			Jx.isArray(

				operation.listeners = this.get(eventObject.name())

			);

	},

	allowCreateEvent: function(operation, name, overrides){

		var config = {

				name: name,
				
				type: name,

				arguments: [],

				target: this.observable

			},

			className = this.getObjectClass(name);

		operation.returnValue = undefined;

		operation.config = config;

		operation.className = className;

		if (Jx.isObject(overrides)){

			Jx.assign(config, overrides);

		}

		return Jx.isString(name, true) &&

			Jx.isString(className, true) &&

			Jx.classExists(className);

	},

	allowCreateListener: function(operation, name, overrides){

		var config = {},

			className = this.getListenerClass(name);

		operation.returnValue = undefined;

		config.type = operation.name = name;

		operation.config = config;

		operation.className = className;

		if (Jx.isObject(overrides)){

			Jx.assign(config, overrides);

		}

		return Jx.isString(name, true) &&

			Jx.isString(className, true) &&

			Jx.classExists(className);

	},
	
	allowSetBubbleTarget: function(operation, target){
		
		return Jx.instanceOf(target, 'Jx.Base') && !target.isDestroyed();
		
	},
	
	allowUnsetBubbleTarget: function(operation){
		
		var target = this.bubblesTo;
		
		return !!target;
		
	},

	isValid: function(name, o){

		return this.$super(arguments) && Jx.isArray(o);

	},

	isValidListenerName: function(name){

		return Jx.isString(name, true) &&

			this.inValidListeners.indexOf(name) == -1 &&

			(!this.strictAccess || !this.accessList.hasOwnProperty(name));

	},

	getListenerClass: function(name){

		var classIndex = this.listenerClassIndex,

			className = this.listenerClass;

		if (Jx.isString(name, true) &&

			classIndex.hasOwnProperty(name) &&

			Jx.classExists(classIndex[name])

		){

			className = classIndex[name];

		}

		return className;

	},

	getObjectClass: function(name){

		var classIndex = this.objectClassIndex,

			className = this.objectClass;

		if (Jx.isString(name, true) &&

			classIndex.hasOwnProperty(name) &&

			Jx.classExists(classIndex[name])

		){

			className = classIndex[name];

		}

		return className;

	},
	
	setEventCapture: function(o){
		
	},

	createEventHandler: function(listener, callback){

		function EventHandlerReceiver( event ){

			var o = arguments.callee,

				listener = o.listener;

			if (!listener.isDestroyed()){

				o.callback( listener, event );

			}

		}

		EventHandlerReceiver.listener = listener;

		EventHandlerReceiver.callback = callback;

		return EventHandlerReceiver;

	},

	formatListeners: function(name, fn, scope, options){

		var listeners = [], listener;

		if (Jx.isObject(name)){

			listeners.self = this;

			//-- check if shared options or not
			Jx.each(name, function(n, v, o){

				var found;

				if (o.hasOwnProperty(n)){

					found = this.self.formatListener(n, v, null, o);

					if (found){

						this.push(found);

					}

				}

			}, listeners);

			delete listeners.self;

		} else if (Jx.isArray(name)){

			for(var o, listener, l = name.length; l--;){

				o = name[l];

				listener = null;

				if (Jx.isObject(o)){

					listener = this.formatListener(o.name, o);

				}

				if (listener){

					listeners[l] = listener;

				} else {

					listeners.splice(l, 1);

				}


			}


		} else if (Jx.isString(name, true)){

			listener = this.formatListener(name, fn, scope, options);

			if (listener){

				listeners.push( listener );

			}

		}

		return listeners;

	},

	formatListener: function(name, fn, scope, options){

		var nil, created;

		created = nil;

		if (this.isValidListenerName(name)){

			if (Jx.isObject(fn) && 'fn' in fn){

				options = fn;

				fn = options.fn;

				scope = options.scope;

			} else if (!Jx.isObject(options)){

				options = {};

			}

			if (Jx.isFunction(fn)){

				created = Jx.assignIf({

					name: name,

					callback: fn,

					options: options,

					scope: scope

				}, options);

			}

		}

		return created;

	}

});
